﻿using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using Box2DX.Common;

namespace Expression.Samples.Physics
{

	public sealed class PhysicsContext
	{
		public static readonly DependencyProperty PhysicsContextProperty = DependencyProperty.RegisterAttached("PhysicsContext", typeof(PhysicsContext), typeof(FrameworkElement), new PropertyMetadata(null, PhysicsContext.HostChanged));

		private Simulator simulator;

		private FrameworkElement host;

		public Simulator Simulator
		{
			get { return this.simulator; }
			private set { this.simulator = value; }
		}

		public double WorldSize
		{
			get;
			set;
		}

		public double Gravity
		{
			get;
			set;
		}

		public double Interval
		{
			get;
			set;
		}

		public bool ShouldStartImmediately
		{
			get;
			set;
		}

		private bool IsPendingHostLoad
		{
			get;
			set;
		}

		

		public PhysicsContext()
		{
			// default values
			this.WorldSize = 25;
			this.Gravity = 10;
			this.Interval = 16.666;
			this.ShouldStartImmediately = true;
		}

		private void Start()
		{
			Debug.Assert(!this.Simulator.IsRunning);
			if (this.Simulator != null)
			{
				this.Simulator.Start();
			}
		}

		private void Stop()
		{
			Debug.Assert(this.Simulator.IsRunning);
			if (this.Simulator != null)
			{
				this.Simulator.Stop();
			}
		}

		private void InitializeSimulator()
		{
			Vector2 screenSize = new Vector2((float)this.Host.ActualWidth, (float)this.Host.ActualHeight);
			float ratio = screenSize.X / screenSize.Y;
			float halfWorldSize = (float)this.WorldSize / 2.0f;
			Vector2 extent = new Vector2(-ratio * halfWorldSize, halfWorldSize);

			this.Simulator = new Simulator(
				this,
				new Vector2(0, 0),
				screenSize,
				extent,
				-extent,
				(float)this.Gravity,
				(float)this.Interval);
			if (this.ShouldStartImmediately)
			{
				this.Host.Dispatcher.BeginInvoke(delegate {
					this.Start();
				});
			}
		}

		private void HookLoaded()
		{
			if (!this.IsPendingHostLoad)
			{
				this.IsPendingHostLoad = true;
				this.Host.Loaded += new RoutedEventHandler(OnHostLoaded);
			}
		}

		private void UnhookLoaded()
		{
			if (this.IsPendingHostLoad)
			{
				this.IsPendingHostLoad = false;
				this.Host.Loaded -= new RoutedEventHandler(OnHostLoaded);
			}
		}

		public FrameworkElement Host {
			get { return this.host; }
			set {
				if (this.host != value) {
					FrameworkElement oldHost = this.Host;
					FrameworkElement newHost = value;

					if (oldHost != null) {
						this.UnhookLoaded();
						this.IsPendingHostLoad = true;
						if (this.Simulator != null) {
							this.Stop();
							this.Simulator = null;
						}
					}
					
					this.host = value;

					if (newHost != null) {
						if (newHost.ActualHeight == 0 && newHost.ActualWidth == 0)
							newHost.SizeChanged += this.SizeChanged;
						else
							this.InitializeSimulator();
					}

					
				}
			}
		}

		private void SizeChanged(object sender, EventArgs e) {
			if (this.Host.ActualWidth != 0 && this.Host.ActualHeight != 0) {
				this.Host.SizeChanged -= this.SizeChanged;

				this.InitializeSimulator();
			}
		}

		void OnHostLoaded(object sender, RoutedEventArgs e)
		{
			this.InitializeSimulator();
		}

		public static PhysicsContext GetPhysicsContext(DependencyObject target) {
			return (PhysicsContext)target.GetValue(PhysicsContext.PhysicsContextProperty);
		}

		public static void SetPhysicsContext(DependencyObject target, PhysicsContext value) {
			target.SetValue(PhysicsContext.PhysicsContextProperty, value);
		}

		private static void HostChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) {
			PhysicsContext newContext = (PhysicsContext)args.NewValue;
			PhysicsContext oldContext = (PhysicsContext)args.OldValue;

			if (newContext != null)
				newContext.Host = target as FrameworkElement;
			if (oldContext != null)
				oldContext.Host = null;
		}

		public static PhysicsContext FindContext(FrameworkElement element) {
			while (element != null) {
				PhysicsContext context = PhysicsContext.GetPhysicsContext(element);
				if (context != null)
					return context;
				element = VisualTreeHelper.GetParent(element) as FrameworkElement;
			}

			return null;
		}
	}
}
